---
title: Calling Rust from the Frontend
i18nReady: true
---

import { Content as FrontendListen } from './_sections/frontend-listen.mdx';

This document includes guides on how to communicate with your Rust code from your application frontend.
To see how to communicate with your frontend from your Rust code, see [Calling the Frontend from Rust].

Tauri provides a [command](#commands) primitive for reaching Rust functions with type safety,
along with an [event system](#event-system) that is more dynamic.

## Commands

Tauri provides a simple yet powerful `command` system for calling Rust functions from your web app.
Commands can accept arguments and return values. They can also return errors and be `async`.

### Basic Example

Commands can be defined in your `src-tauri/src/lib.rs` file.
To create a command, just add a function and annotate it with `#[tauri::command]`:

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
fn my_custom_command() {
	println!("I was invoked from JavaScript!");
}
```

:::note
Command names must be unique.
:::

:::note
Commands defined in the `lib.rs` file cannot be marked as `pub` due to a limitation in the glue code generation.
You will see an error like this if you mark it as a public function:

```
error[E0255]: the name `__cmd__command_name` is defined multiple times
  --> src/lib.rs:28:8
   |
27 | #[tauri::command]
   | ----------------- previous definition of the macro `__cmd__command_name` here
28 | pub fn x() {}
   |        ^ `__cmd__command_name` reimported here
   |
   = note: `__cmd__command_name` must be defined only once in the macro namespace of this module
```

:::

You will have to provide a list of your commands to the builder function like so:

```rust title="src-tauri/src/lib.rs" ins={4}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
	tauri::Builder::default()
		.invoke_handler(tauri::generate_handler![my_custom_command])
		.run(tauri::generate_context!())
		.expect("error while running tauri application");
}
```

Now, you can invoke the command from your JavaScript code:

```javascript
// When using the Tauri API npm package:
import { invoke } from '@tauri-apps/api/core';

// When using the Tauri global script (if not using the npm package)
// Be sure to set `app.withGlobalTauri` in `tauri.conf.json` to true
const invoke = window.__TAURI__.core.invoke;

// Invoke the command
invoke('my_custom_command');
```

#### Defining Commands in a Separate Module

If your application defines a lot of components or if they can be grouped,
you can define commands in a separate module instead of bloating the `lib.rs` file.

As an example let's define a command in the `src-tauri/src/commands.rs` file:

```rust title="src-tauri/src/commands.rs"
#[tauri::command]
pub fn my_custom_command() {
	println!("I was invoked from JavaScript!");
}
```

:::note
When defining commands in a separate module they should be marked as `pub`.
:::

:::note
The command name is not scoped to the module so they must be unique even between modules.
:::

In the `lib.rs` file, define the module and provide the list of your commands accordingly;

```rust title="src-tauri/src/lib.rs" ins={6}
mod commands;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
	tauri::Builder::default()
		.invoke_handler(tauri::generate_handler![commands::my_custom_command])
		.run(tauri::generate_context!())
		.expect("error while running tauri application");
}
```

Note the `commands::` prefix in the command list, which denotes the full path to the command function.

The command name in this example is `my_custom_command` so you can still call it by executing `invoke("my_custom_command")`
in your frontend, the `commands::` prefix is ignored.

#### WASM

When using a Rust frontend to call `invoke()` without arguments, you will need to adapt your frontend code as below.
The reason is that Rust doesn't support optional arguments.

```rust ins={4-5}
#[wasm_bindgen]
extern "C" {
    // invoke without arguments
    #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name = invoke)]
    async fn invoke_without_args(cmd: &str) -> JsValue;

    // invoke with arguments (default)
    #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])]
    async fn invoke(cmd: &str, args: JsValue) -> JsValue;

    // They need to have different names!
}
```

### Passing Arguments

Your command handlers can take arguments:

```rust
#[tauri::command]
fn my_custom_command(invoke_message: String) {
	println!("I was invoked from JavaScript, with this message: {}", invoke_message);
}
```

Arguments should be passed as a JSON object with camelCase keys:

```javascript
invoke('my_custom_command', { invokeMessage: 'Hello!' });
```

:::note
You can use `snake_case` for the arguments with the `rename_all` attribute:

```rust
#[tauri::command(rename_all = "snake_case")]
fn my_custom_command(invoke_message: String) {}
```

The corresponding JavaScript:

```javascript
invoke('my_custom_command', { invoke_message: 'Hello!' });
```

:::

Arguments can be of any type, as long as they implement [`serde::Deserialize`].

### Returning Data

Command handlers can return data as well:

```rust
#[tauri::command]
fn my_custom_command() -> String {
	"Hello from Rust!".into()
}
```

The `invoke` function returns a promise that resolves with the returned value:

```javascript
invoke('my_custom_command').then((message) => console.log(message));
```

Returned data can be of any type, as long as it implements [`serde::Serialize`].

#### Returning Array Buffers

Return values that implements [`serde::Serialize`] are serialized to JSON when the response is sent to the frontend.
This can slow down your application if you try to return a large data such as a file or a download HTTP response.
To return array buffers in an optimized way, use [`tauri::ipc::Response`]:

```rust
use tauri::ipc::Response;
#[tauri::command]
fn read_file() -> Response {
	let data = std::fs::read("/path/to/file").unwrap();
	tauri::ipc::Response::new(data)
}
```

### Error Handling

If your handler could fail and needs to be able to return an error, have the function return a `Result`:

```rust
#[tauri::command]
fn login(user: String, password: String) -> Result<String, String> {
	if user == "tauri" && password == "tauri" {
		// resolve
		Ok("logged_in".to_string())
	} else {
		// reject
		Err("invalid credentials".to_string())
	}
}
```

If the command returns an error, the promise will reject, otherwise, it resolves:

```javascript
invoke('login', { user: 'tauri', password: '0j4rijw8=' })
  .then((message) => console.log(message))
  .catch((error) => console.error(error));
```

As mentioned above, everything returned from commands must implement [`serde::Serialize`], including errors.
This can be problematic if you're working with error types from Rust's std library or external crates as most error types do not implement it.
In simple scenarios you can use `map_err` to convert these errors to `String`:

```rust
#[tauri::command]
fn my_custom_command() -> Result<(), String> {
	std::fs::File::open("path/to/file").map_err(|err| err.to_string())?;
	// Return `null` on success
	Ok(())
}
```

Since this is not very idiomatic you may want to create your own error type which implements `serde::Serialize`.
In the following example, we use the [`thiserror`] crate to help create the error type.
It allows you to turn enums into error types by deriving the `thiserror::Error` trait.
You can consult its documentation for more details.

```rust
// create the error type that represents all errors possible in our program
#[derive(Debug, thiserror::Error)]
enum Error {
	#[error(transparent)]
	Io(#[from] std::io::Error)
}

// we must manually implement serde::Serialize
impl serde::Serialize for Error {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: serde::ser::Serializer,
	{
		serializer.serialize_str(self.to_string().as_ref())
	}
}

#[tauri::command]
fn my_custom_command() -> Result<(), Error> {
	// This will return an error
	std::fs::File::open("path/that/does/not/exist")?;
	// Return `null` on success
	Ok(())
}
```

A custom error type has the advantage of making all possible errors explicit so readers can quickly identify what errors can happen.
This saves other people (and yourself) enormous amounts of time when reviewing and refactoring code later.<br/>
It also gives you full control over the way your error type gets serialized.
In the above example, we simply returned the error message as a string, but you could assign each error a code
so you could more easily map it to a similar looking TypeScript error enum for example:

```rust
#[derive(Debug, thiserror::Error)]
enum Error {
  #[error(transparent)]
  Io(#[from] std::io::Error),
  #[error("failed to parse as string: {0}")]
  Utf8(#[from] std::str::Utf8Error),
}

#[derive(serde::Serialize)]
#[serde(tag = "kind", content = "message")]
#[serde(rename_all = "camelCase")]
enum ErrorKind {
  Io(String),
  Utf8(String),
}

impl serde::Serialize for Error {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
    S: serde::ser::Serializer,
  {
    let error_message = self.to_string();
    let error_kind = match self {
      Self::Io(_) => ErrorKind::Io(error_message),
      Self::Utf8(_) => ErrorKind::Utf8(error_message),
    };
    error_kind.serialize(serializer)
  }
}

#[tauri::command]
fn read() -> Result<Vec<u8>, Error> {
  let data = std::fs::read("/path/to/file")?;
	Ok(data)
}
```

In your frontend you now get a `{ kind: 'io' | 'utf8', message: string }` error object:

```ts
type ErrorKind = {
  kind: 'io' | 'utf8';
  message: string;
};

invoke('read').catch((e: ErrorKind) => {});
```

### Async Commands

Asynchronous commands are preferred in Tauri to perform heavy work in a manner that doesn't result in UI freezes or slowdowns.

:::note

Async commands are executed on a separate async task using [`async_runtime::spawn`].
Commands without the _async_ keyword are executed on the main thread unless defined with _#[tauri::command(async)]_.

:::

**If your command needs to run asynchronously, simply declare it as `async`.**

:::caution

You need to be careful when creating asynchronous functions using Tauri.
Currently, you cannot simply include borrowed arguments in the signature of an asynchronous function.
Some common examples of types like this are `&str` and `State<'_, Data>`.
This limitation is tracked here: https://github.com/tauri-apps/tauri/issues/2533 and workarounds are shown below.

:::

When working with borrowed types, you have to make additional changes. These are your two main options:

**Option 1**: Convert the type, such as `&str` to a similar type that is not borrowed, such as `String`.
This may not work for all types, for example `State<'_, Data>`.

_Example:_

```rust
// Declare the async function using String instead of &str, as &str is borrowed and thus unsupported
#[tauri::command]
async fn my_custom_command(value: String) -> String {
	// Call another async function and wait for it to finish
	some_async_function().await;
	value
}
```

**Option 2**: Wrap the return type in a [`Result`]. This one is a bit harder to implement, but works for all types.

Use the return type `Result<a, b>`, replacing `a` with the type you wish to return, or `()` if you wish to return `null`, and replacing `b` with an error type to return if something goes wrong, or `()` if you wish to have no optional error returned. For example:

- `Result<String, ()>` to return a String, and no error.
- `Result<(), ()>` to return `null`.
- `Result<bool, Error>` to return a boolean or an error as shown in the [Error Handling](#error-handling) section above.

_Example:_

```rust
// Return a Result<String, ()> to bypass the borrowing issue
#[tauri::command]
async fn my_custom_command(value: &str) -> Result<String, ()> {
	// Call another async function and wait for it to finish
	some_async_function().await;
	// Note that the return value must be wrapped in `Ok()` now.
	Ok(format!(value))
}
```

##### Invoking from JavaScript

Since invoking the command from JavaScript already returns a promise, it works just like any other command:

```javascript
invoke('my_custom_command', { value: 'Hello, Async!' }).then(() =>
  console.log('Completed!')
);
```

### Channels

The Tauri channel is the recommended mechanism for streaming data such as streamed HTTP responses to the frontend.
The following example reads a file and notifies the frontend of the progress in chunks of 4096 bytes:

```rust
use tokio::io::AsyncReadExt;

#[tauri::command]
async fn load_image(path: std::path::PathBuf, reader: tauri::ipc::Channel<&[u8]>) {
  // for simplicity this example does not include error handling
  let mut file = tokio::fs::File::open(path).await.unwrap();

  let mut chunk = vec![0; 4096];

  loop {
    let len = file.read(&mut chunk).await.unwrap();
    if len == 0 {
      // Length of zero means end of file.
      break;
    }
    reader.send(&chunk).unwrap();
  }
}
```

See the [channels documentation] for more information.

### Accessing the WebviewWindow in Commands

Commands can access the `WebviewWindow` instance that invoked the message:

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
async fn my_custom_command(webview_window: tauri::WebviewWindow) {
	println!("WebviewWindow: {}", webview_window.label());
}
```

### Accessing an AppHandle in Commands

Commands can access an `AppHandle` instance:

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
async fn my_custom_command(app_handle: tauri::AppHandle) {
	let app_dir = app_handle.path().app_dir();
	use tauri::GlobalShortcutManager;
	app_handle.global_shortcut_manager().register("CTRL + U", move || {});
}
```

:::tip

`AppHandle` and `WebviewWindow` both take a generic parameter `R: Runtime`,
when the `wry` feature is enabled in `tauri` (which is enabled by default),
we default the generic to the `Wry` runtime so you can use it directly,
but if you want to use a different runtime, for example the [mock runtime],
you need to write your functions like this

```rust title="src-tauri/src/lib.rs" ins="<R: Runtime>" ins="<R>"
use tauri::{AppHandle, GlobalShortcutManager, Runtime, WebviewWindow};

#[tauri::command]
async fn my_custom_command<R: Runtime>(app_handle: AppHandle<R>, webview_window: WebviewWindow<R>) {
  let app_dir = app_handle.path().app_dir();
  app_handle
    .global_shortcut_manager()
    .register("CTRL + U", move || {});
  println!("WebviewWindow: {}", webview_window.label());
}
```

:::

### Accessing Managed State

Tauri can manage state using the `manage` function on `tauri::Builder`.
The state can be accessed on a command using `tauri::State`:

```rust title="src-tauri/src/lib.rs"
struct MyState(String);

#[tauri::command]
fn my_custom_command(state: tauri::State<MyState>) {
	assert_eq!(state.0 == "some state value", true);
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
	tauri::Builder::default()
		.manage(MyState("some state value".into()))
		.invoke_handler(tauri::generate_handler![my_custom_command])
		.run(tauri::generate_context!())
		.expect("error while running tauri application");
}
```

### Accessing Raw Request

Tauri commands can also access the full [`tauri::ipc::Request`] object which includes the raw body payload and the request headers.

```rust
#[derive(Debug, thiserror::Error)]
enum Error {
  #[error("unexpected request body")]
  RequestBodyMustBeRaw,
  #[error("missing `{0}` header")]
  MissingHeader(&'static str),
}

impl serde::Serialize for Error {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
    S: serde::ser::Serializer,
  {
    serializer.serialize_str(self.to_string().as_ref())
  }
}

#[tauri::command]
fn upload(request: tauri::ipc::Request) -> Result<(), Error> {
  let tauri::ipc::InvokeBody::Raw(upload_data) = request.body() else {
    return Err(Error::RequestBodyMustBeRaw);
  };
  let Some(authorization_header) = request.headers().get("Authorization") else {
    return Err(Error::MissingHeader("Authorization"));
  };

  // upload...

  Ok(())
}
```

In the frontend you can call invoke() sending a raw request body by providing an ArrayBuffer or Uint8Array on the payload argument,
and include request headers in the third argument:

```js
const data = new Uint8Array([1, 2, 3]);
await __TAURI__.core.invoke('upload', data, {
  headers: {
    Authorization: 'apikey',
  },
});
```

### Creating Multiple Commands

The `tauri::generate_handler!` macro takes an array of commands. To register
multiple commands, you cannot call invoke_handler multiple times. Only the last
call will be used. You must pass each command to a single call of
`tauri::generate_handler!`.

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
fn cmd_a() -> String {
	"Command a"
}
#[tauri::command]
fn cmd_b() -> String {
	"Command b"
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
	tauri::Builder::default()
		.invoke_handler(tauri::generate_handler![cmd_a, cmd_b])
		.run(tauri::generate_context!())
		.expect("error while running tauri application");
}
```

### Complete Example

Any or all of the above features can be combined:

```rust title="src-tauri/src/lib.rs"
struct Database;

#[derive(serde::Serialize)]
struct CustomResponse {
	message: String,
	other_val: usize,
}

async fn some_other_function() -> Option<String> {
	Some("response".into())
}

#[tauri::command]
async fn my_custom_command(
	window: tauri::Window,
	number: usize,
	database: tauri::State<'_, Database>,
) -> Result<CustomResponse, String> {
	println!("Called from {}", window.label());
	let result: Option<String> = some_other_function().await;
	if let Some(message) = result {
		Ok(CustomResponse {
			message,
			other_val: 42 + number,
		})
	} else {
		Err("No result".into())
	}
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
	tauri::Builder::default()
		.manage(Database {})
		.invoke_handler(tauri::generate_handler![my_custom_command])
		.run(tauri::generate_context!())
		.expect("error while running tauri application");
}
```

```javascript
import { invoke } from '@tauri-apps/api/core';

// Invocation from JavaScript
invoke('my_custom_command', {
  number: 42,
})
  .then((res) =>
    console.log(`Message: ${res.message}, Other Val: ${res.other_val}`)
  )
  .catch((e) => console.error(e));
```

## Event System

The event system is a simpler communication mechanism between your frontend and the Rust.
Unlike commands, events are not type safe, are always async, cannot return values and only supports JSON payloads.

### Global Events

To trigger a global event you can use the [event.emit] or the [WebviewWindow#emit] functions:

```js
import { emit } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';

// emit(eventName, payload)
emit('file-selected', '/path/to/file');

const appWebview = getCurrentWebviewWindow();
appWebview.emit('route-changed', { url: window.location.href });
```

:::note
Global events are delivered to **all** listeners
:::

### Webview Event

To trigger an event to a listener registered by a specific webview you can use the [event.emitTo] or the [WebviewWindow#emitTo] functions:

```js
import { emitTo } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';

// emitTo(webviewLabel, eventName, payload)
emitTo('settings', 'settings-update-requested', {
  key: 'notification',
  value: 'all',
});

const appWebview = getCurrentWebviewWindow();
appWebview.emitTo('editor', 'file-changed', {
  path: '/path/to/file',
  contents: 'file contents',
});
```

:::note
Webview-specific events are **not** triggered to regular global event listeners.
To listen to **any** event you must provide the `{ target: { kind: 'Any' } }` option to the [event.listen] function,
which defines the listener to act as a catch-all for emitted events:

```js
import { listen } from '@tauri-apps/api/event';
listen(
  'state-changed',
  (event) => {
    console.log('got state changed event', event);
  },
  {
    target: { kind: 'Any' },
  }
);
```

:::

### Listening to Events

<FrontendListen />

To learn how to listen to events and emit events from your Rust code, see the [Rust Event System documentation].

[Calling the Frontend from Rust]: /develop/calling-frontend/
[`async_runtime::spawn`]: https://docs.rs/tauri/2.0.0/tauri/async_runtime/fn.spawn.html
[`serde::serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
[`serde::deserialize`]: https://docs.serde.rs/serde/trait.Deserialize.html
[`tauri::ipc::Response`]: https://docs.rs/tauri/2.0.0/tauri/ipc/struct.Response.html
[`tauri::ipc::Request`]: https://docs.rs/tauri/2.0.0/tauri/ipc/struct.Request.html
[`thiserror`]: https://github.com/dtolnay/thiserror
[`result`]: https://doc.rust-lang.org/std/result/index.html
[event.emit]: /reference/javascript/api/namespaceevent/#emit
[event.listen]: /reference/javascript/api/namespaceevent/#listen
[WebviewWindow#emit]: /reference/javascript/api/namespacewebviewwindow/#emit
[event.emitTo]: /reference/javascript/api/namespaceevent/#emitto
[WebviewWindow#emitTo]: /reference/javascript/api/namespacewebviewwindow/#emitto
[Rust Event System documentation]: /develop/calling-frontend/#event-system
[channels documentation]: /develop/calling-frontend/#channels
[Calling Rust from the Frontend]: /develop/calling-rust/
[mock runtime]: https://docs.rs/tauri/2.0.0/tauri/test/struct.MockRuntime.html
